package com.zy.multiredis.config;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Modifier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

public class MuitiRedisTemplateInjectAnnotationBeanPostProcessor implements BeanPostProcessor,BeanFactoryAware {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    private boolean requiredParameterValue = true;
    
    private String requiredParameterName = "required";
    
    private String beanNameParameterName = "injectName";
    
    private ConfigurableListableBeanFactory beanFactory;
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        ReflectionUtils.doWithFields(clazz, field -> {
            AnnotationAttributes ann = findAutowiredAnnotation(field);
            if (ann != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("MuitiRedisTemplateInject annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                String injectName = determineBeanName(ann);
                if(StringUtils.isEmpty(injectName)) {
                    logger.info("MuitiRedisTemplateInject annotation 'injectName' value can not be null!");
                    return;
                }else {
                    Object value = beanFactory.getBean(injectName);
                    if(value == null && required) {
                        logger.info("MuitiRedisTemplateInject annotation can not find "+injectName+" instance for 'injectName'!");
                        return;
                    }
                    if(value != null) {
                        field.setAccessible(true);
                        field.set(bean, value);
                    }
                }
            }
        });
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
        return o;
    }
    
    @Nullable
    private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
        if (ao.getAnnotations().length > 0) { // autowiring annotations have to be local
            AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, LazyInject.class);
            if (attributes != null) {
                return attributes;
            }
        }
        return null;
    }

    protected boolean determineRequiredStatus(AnnotationAttributes ann) {
        return (!ann.containsKey(this.requiredParameterName) ||
                this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
    }
    
    protected String determineBeanName(AnnotationAttributes ann) {
        return ann.getString(beanNameParameterName);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "MuitiRedisTemplateInjectAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
        }
        this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
    }
}